<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
 * Google Maps API integration.
 *
 * $Id: Gmap.php 4302 2009-05-01 02:49:41Z kiall $
 *
 * @package    Gmaps
 * @author     Kohana Team
 * @copyright  (c) 2007-2008 Kohana Team
 * @license    http://kohanaphp.com/license.html
 */
class Gmap_Core {

	// Map settings
	protected $id;
	protected $options;
	protected $center;
	protected $control;
	protected $overview_control;
	protected $type_control = FALSE;

	// Map types
	protected $types = array();
	protected $default_types = array
	(
		'G_NORMAL_MAP','G_SATELLITE_MAP','G_HYBRID_MAP','G_PHYSICAL_MAP'
		);

		// Markers icons
		protected $icons = array();

		// Map markers
		protected $markers = array();

		/**
		 * Set the GMap center point.
		 *
		 * @param string $id HTML map id attribute
		 * @param array $options array of GMap constructor options
		 * @return void
		 */
		public function __construct($id = 'map', $options = NULL)
		{
			// Set map ID and options
			$this->id = $id;
			$this->options = new Gmap_Options((array) $options);
		}

		/**
		 * Return GMap javascript url
		 *
		 * @param   string  API component
		 * @param   array   API parameters
		 * @return  string
		 */
	 public static function api_url($component = 'jsapi', $parameters = NULL, $separator = '&amp;')
	 {
	 	if (empty($parameters['ie']))
	 	{
	 		// Set input encoding to UTF-8
	 		$parameters['ie'] = 'utf-8';
	 	}

	 	if (empty($parameters['oe']))
	 	{
	 		// Set ouput encoding to input encoding
	 		$parameters['oe'] = $parameters['ie'];
	 	}

	 	if (empty($parameters['key']))
	 	{
	 		// Set the API key last
	 		$parameters['key'] = Kohana::config('gmaps.api_key');
	 	}

	 	return 'http://'.Kohana::config('gmaps.api_domain').'/'.$component.'?'.http_build_query($parameters, '', $separator);
	 }

	 /**
	  * Retrieves the latitude and longitude of an address.
	  *
	  * @param string $address address
	  * @return array longitude, latitude
	  */
	 public static function address_to_ll($address)
	 {
	 	$lat = NULL;
	 	$lon = NULL;

	 	if ($xml = Gmap::address_to_xml($address))
	 	{
	 		// Get the latitude and longitude from the Google Maps XML
	 		// NOTE: the order (lon, lat) is the correct order
	 		list ($lon, $lat) = explode(',', $xml->Response->Placemark->Point->coordinates);
	 	}

	 	return array($lat, $lon);
	 }

	 /**
	  * Retrieves the XML geocode address lookup.
	  * ! Results of this method are cached for 1 day.
	  *
	  * @param string $address adress
	  * @return object SimpleXML
	  */
	 public static function address_to_xml($address)
	 {
	 	static $cache;

	 	// Load Cache
	 	if ($cache === NULL)
	 	{
	 		$cache = Cache::instance();
	 	}

	 	// Address cache key
	 	$key = 'gmap-address-'.sha1($address);

	 	if ($xml = $cache->get($key))
	 	{
	 		// Return the cached XML
	 		return simplexml_load_string($xml);
	 	}
	 	else
	 	{
	 		// Setup the retry counter and retry delay
	 		$remaining_retries = Kohana::config('gmaps.retries');
	 		$retry_delay = Kohana::config('gmaps.retry_delay');

	 		// Set the XML URL
	 		$xml_url = Gmap::api_url('maps/geo', array('output' => 'xml', 'q' => $address), '&');

	 		// Disable error reporting while fetching the feed
	 		$ER = error_reporting(~E_NOTICE);

	 		// Enter the request/retry loop.
	 		while ($remaining_retries)
	 		{
	 			// Load the XML
	 			$xml = simplexml_load_file($xml_url);

	 			if (is_object($xml) AND ($xml instanceof SimpleXMLElement) AND (int) $xml->Response->Status->code === 200)
	 			{
	 				// Cache the XML
	 				$cache->set($key, $xml->asXML(), array('gmaps'), 86400);

	 				// Since the geocode was successful, theres no need to try again
	 				$remaining_retries = 0;
	 			}
	 			elseif ((int) $xml->Response->Status->code === 620)
	 			{
	 				/* Goole is rate limiting us - either we're making too many requests too fast, or
	 				 * we've exceeded the 15k per 24hour limit. */

	 				// Reduce the number of remaining retries
	 				$remaining_retries--;
	 				if ( ! $remaining_retries)
	 				return FALSE;

				 	// Sleep for $retry_delay microseconds before trying again.
				 	usleep($retry_delay);
	 			}
	 			else
	 			{
	 				// Invalid XML response
	 				$xml = FALSE;

	 				// Dont retry.
	 				$remaining_retries = 0;
	 			}
	 		}

	 		// Turn error reporting back on
	 		error_reporting($ER);
	 	}

	 	return $xml;
	 }

	 /**
	  * Returns an image map
	  *
	  * @param mixed $lat latitude or an array of marker points
	  * @param float $lon longitude
	  * @param integer $zoom zoom level (1-19)
	  * @param string $type map type (roadmap or mobile)
	  * @param integer $width map width
	  * @param integer $height map height
	  * @return string
	  */
	 public static function static_map($lat = 0, $lon = 0, $zoom = 6, $type = NULL, $width = 300, $height = 300)
	 {
	 	// Valid map types
	 	$types = array('roadmap', 'mobile');

	 	// Maximum width and height are 640px
	 	$width = min(640, abs($width));
	 	$height = min(640, abs($height));

	 	$parameters['size'] = $width.'x'.$height;

	 	// Minimum zoom = 0, maximum zoom = 19
	 	$parameters['zoom'] = max(0, min(19, abs($zoom)));

	 	if (in_array($type, $types))
	 	{
	 		// Set map type
	 		$parameters['maptype'] = $type;
	 	}

	 	if (is_array($lat))
	 	{
	 		foreach ($lat as $_lat => $_lon)
	 		{
	 			$parameters['markers'][] = $_lat.','.$_lon;
	 		}

	 		$parameters['markers'] = implode('|', $parameters['markers']);
	 	}
	 	else
	 	{
	 		$parameters['center'] = $lat.','.$lon;
	 	}

	 	return Gmap::api_url('staticmap', $parameters);
	 }

	 /**
	  * Set the GMap center point.
	  *
	  * @chainable
	  * @param float $lat latitude
	  * @param float $lon longitude
	  * @param integer $zoom zoom level (1-19)
	  * @param string $type default map type
	  * @return object
	  */
	 public function center($lat, $lon, $zoom = 6, $type = 'G_NORMAL_MAP')
	 {
	 	$zoom = max(0, min(19, abs($zoom)));
	 	$type = ($type != 'G_NORMAL_MAP' AND in_array($type, $this->default_types, true)) ? $type : 'G_NORMAL_MAP';

	 	// Set center location, zoom and default map type
	 	$this->center = array($lat, $lon, $zoom, $type);

	 	return $this;
	 }

	 /**
	  * Set the GMap controls size.
	  *
	  * @chainable
	  * @param string $size small or large
	  * @return object
	  */
	 public function controls($size = NULL)
	 {
	 	// Set the control type
	 	$this->control = (strtolower($size) == 'small') ? 'Small' : 'Large';

	 	return $this;
	 }

	 /**
	  * Set the GMap overview map.
	  *
	  * @chainable
	  * @param integer $width width
	  * @param integer $height height
	  * @return object
	  */
	 public function overview($width = '', $height = '')
	 {
	 	$size = (is_int($width) AND is_int($height)) ? 'new GSize('.$width.','.$height.')' : '';
	 	$this->overview_control = 'map.addControl(new google.maps.OverviewMapControl('.$size.'));';

	 	return $this;
	 }

	 /**
	  * Set the GMap type controls.
	  * by default renders G_NORMAL_MAP, G_SATELLITE_MAP, and G_HYBRID_MAP
	  *
	  * @chainable
	  * @param string $type map type
	  * @param string $action add or remove map type
	  * @return object
	  */
	 public function types($type = NULL, $action = 'remove')
	 {
	 	$this->type_control = TRUE;

	 	if ($type !== NULL AND in_array($type, $this->default_types, true))
	 	{
	 		// Set the map type and action
	 		$this->types[$type] = (strtolower($action) == 'remove') ? 'remove' : 'add';
	 	}

	 	return $this;
	 }

	 /**
	  * Create a custom marker icon
	  *
	  * @chainable
	  * @param string $name icon name
	  * @param array $options icon options
	  * @return object
	  */
	 public function add_icon($name, array $options)
	 {
	 	// Add a new cusotm icon
	 	$this->icons[] = new Gmap_Icon($name, $options);

	 	return $this;
	 }

	 /**
	  * Set the GMap marker point.
	  *
	  * @chainable
	  * @param float $lat latitude
	  * @param float $lon longitude
	  * @param string $html HTML for info window
	  * @param array $options marker options
	  * @return object
	  */
	 public function add_marker($lat, $lon, $html = '', $options = array())
	 {
	 	// Add a new marker
	 	$this->markers[] = new Gmap_Marker($lat, $lon, $html, $options);

	 	return $this;
	 }

	 /**
	  * Render the map into GMap Javascript.
	  *
	  * @param string $template template name
	  * @param array $extra extra fields passed to the template
	  * @return string
	  */
	 public function render($template = 'gmaps/javascript', $extra = array())
	 {
	 	// Latitude, longitude, zoom and default map type
	 	list ($lat, $lon, $zoom, $default_type) = $this->center;

	 	// Map
	 	$map = 'var map = new google.maps.Map2(document.getElementById("'.$this->id.'"));';

	 	// Map controls
	 	$controls[] = empty($this->control) ? '' : 'map.addControl(new google.maps.'.$this->control.'MapControl());';

	 	// Map Types
	 	if ($this->type_control === TRUE)
	 	{
	 		if (count($this->types) > 0)
	 		{
	 			foreach($this->types as $type => $action)
					$controls[] = 'map.'.$action.'MapType('.$type.');';
	 		}

	 		$controls[] = 'map.addControl(new google.maps.MapTypeControl());';
	 	}

	 	if ( ! empty($this->overview_control))
			$controls[] = $this->overview_control;

			// Map centering
			$center = 'map.setCenter(new google.maps.LatLng('.$lat.', '.$lon.'), '.$zoom.', '.$default_type.');';

			$data = array_merge($extra, array
			(
				'map' => $map,
				'options' => $this->options,
				'controls' => implode("\n", $controls),
				'center' => $center,
				'icons' => $this->icons,
				'markers' => $this->markers,
			));

			// Render the Javascript
			return View::factory($template, $data)->render();
	 }

} // End Gmap